#include "conf.h"
#include "main.h"
#include "people_id_file.h"
#include "huzhou_common.h"
#include "ServerThread.h"

#include "ros/ros.h"
#include <ros/callback_queue.h>
#include <std_msgs/String.h>
#include <std_srvs/Empty.h>
#include <stdlib.h>

#include "region_remap.h"

#include "llm_node/llm_tts.h"
#include "llm_node/llm_srv.h"
#include "llm_node/llm_asr.h"
#include "llm_node/llm_goBack.h"

#include "srvbg/people_id_lan.h"

#include <regex>
#include <chrono>
#include <thread>
#include <string>
#include <filesystem>

// sudo apt install mpg123


constexpr const char* say_hello_path = "/opt/sherpa-onnx/say_hello/";



namespace mmain
{
    ros::ServiceClient tts_client;

    ros::ServiceClient kws_client;

    ros::ServiceClient asr_client;

    ros::ServiceClient willing_client;

    ros::ServiceClient dialog_client;

    ros::Publisher req_pub;

    ros::ServiceClient llm_client;

    std::unique_ptr<PatientManager> patient_manager;

    std::unique_ptr<RegionRemap> region_remap;


    void sendStrToTTS(std::string data, bool is_block)
    {
        llm_node::llm_tts srv;
        srv.request.data = data;
        srv.request.block = is_block;

        tts_client.call(srv);
    }


    void sendStrToLLM(std::string question)
    {
        sys_log("%s", question.c_str());
        llm_node::llm_srv srv;
        srv.request.question = question;

        llm_client.call(srv);
    }


    // 接受来自 大模型的输出， 将大模型的输出转为 语音进行合成
    void llmRecStreamData(const std_msgs::String::ConstPtr &msg_p)
    {

        sys_log("rec: %s", msg_p->data.c_str());

        // sendStrToTTS(msg_p->data, false);

        // 判断是否是结束，如果以 "[DONE]" 开头的话，就说明大模型的回答已经结束了
        if (msg_p->data.size() >= 6 && msg_p->data.compare(0, 6, "[DONE]") == 0)
        {
            // sys_log("llm server done, notify input finished.");
            // hzc::tts->notifyInputFinished();
            return;

        } else {
            // 添加到语音合成的 buffer 里面，进行语音的合成
            sendStrToTTS(msg_p->data, false);
        }
    }


    bool mode_visite_server(std::string &people_id)
    {

        int id = -1;
        std::string name, region;

        // 获取病人的名称和所在省份
        try {
            id = std::stoi(people_id);

            // 根据 id编号 来获取患者的信息
            if (auto patient = patient_manager->findById(id)) {
                name = patient->get().name;
                region = patient->get().region;
            } else {
                err_log("can not find the patient with id(%d).", id);
            }
        } catch (const std::invalid_argument &e) {
            err_log("Invalid argument: people_id is not a number: %s", people_id.c_str());
        }


        bool playAudioFlag = false;
        // 播放方言进行打招呼
        if (region_remap && !region.empty()) {
            std::string audioName = region_remap->lookup(region);

            if (!audioName.empty()) {
                // 音频文件的绝对路径
                std::string audioRealPath = say_hello_path + region_remap->lookup(region);
                sys_log("audio path: %s", audioRealPath.c_str());

                // 检查文件是否真的存在
                if (!audioName.empty() && std::filesystem::exists(audioRealPath)) {

                    playAudioFlag = true;

                    // 播放病人名称
                    if (!name.empty()) {
                        sendStrToTTS(name + "患者，", true);
                    }

                    std::string cmd = "mpg123 " + audioRealPath;
                    system(cmd.c_str());
                } else {
                    sys_log("audio file not found: %s", audioRealPath.c_str());
                }
            } else {
                warn_log("can not find region name in regionRemap.");
            }
        } else {
            warn_log("region(%s) may be empty.", region.c_str());
        }

        if (!playAudioFlag) {
            // 播放病人名称
            if (!name.empty()) {
                sendStrToTTS(name + "患者，", false);
            }
            // 没有播放方言就播放普通话版本
            sendStrToTTS("您好，我是“小知同学”，是前来巡诊的机器人。", false);
            warn_log("paly Mandarin Chinese.");
        }


        sendStrToLLM("你是在血液透析病房巡诊的机器人，病人正在做血液透析，"
                    "用三句话向患者介绍肾病的注意事项，"
                    "以“我先向您普及一下肾病的注意事项，”开头。不要跟患者打招呼。");

        sendStrToLLM("你是在血液透析病房巡诊的机器人，病人正在做血液透析，"
            "用两句话提醒患者不要忘记做康复运动（不是剧烈运动，不要推荐游泳），"
            "以“请您不要忘记”开头。不要跟患者打招呼。");

        sendStrToTTS("您有问题要询问吗？可以叫我‘小知同学’来进行询问呦。", true);

        // 发送对话服务，进行人机对话
        std_srvs::Empty srv;
        mmain::dialog_client.call(srv);

        sendStrToTTS("如果您没有其他问题，小知同学就先告辞啦。"
                     "希望今天的陪伴能为您带来一些帮助，祝您生活顺心，早日康复，我们下次再见。",
                     true);

        sys_log("mode visite server done......");

        return true;
    }

    bool mode_recept_server(std::string &people_id) {

        bool patientWillingGoback = false;

        while (!patientWillingGoback && ros::ok()) {

            // 先进行关键词识别
            std_srvs::Empty srv;
            kws_client.call(srv);

            sendStrToTTS("您好，我在呢。", true);

            // 再进行语音识别
            llm_node::llm_asr asr_srv;
            asr_client.call(asr_srv);

            std::string asrRes = asr_srv.response.res;

            sys_log("capture: %s", asrRes.c_str());

            // 说点套话
            sendStrToTTS("小知同学听见啦，您刚才说的是，" + asrRes + 
                         "，我正在思考中，请您稍等一下。", 
                         false);

            // 检查 willing
            llm_node::llm_goBack goBack_srv;
            goBack_srv.request.asrRes = asrRes;
            willing_client.call(goBack_srv);

            patientWillingGoback = goBack_srv.response.willing;
            std::string reason = goBack_srv.response.llmRet;

            if (patientWillingGoback) {
                sendStrToTTS("好的，那我们一起走回病房吧，小知同学会一直陪着您，不用着急，注意安全。", true);
                break;
            } else {
                // 询问一下是不是想要回去
                sendStrToTTS("您是否已经完成这边的事情，需要我带您回病房吗？", true);
                continue;
            }
        }

        return true;
    }


    // 接受一个字符串，格式是"mode_id"，比如："recept_1", "visite_1"
    // TODO:
    // bool medicalNodeHandler(llm_node::people_id_lan::Request &req,
    //                         llm_node::people_id_lan::Response &ret)
    bool medicalNodeHandler(srvbg::people_id_lan::Request &req,
                            srvbg::people_id_lan::Response &ret)
    {

        sys_log("llm node server received signal");

        // 使用正则表达式进行匹配
        std::regex pattern(R"((\d+)_([a-zA-Z]+))");
        std::smatch match;
        if (std::regex_match(req.id_mode, match, pattern))
        {

            std::string people_id = match[1]; // 患者的编号

            std::string mode = match[2]; // 接诊模式还是巡诊模式

            sys_log("people id: %s, mode: %s", people_id.c_str(), mode.c_str());

            // 检查 mode 参数
            if (mode == "recept")
            { // 接诊模式
                sys_log("recept mode get!");
                mode_recept_server(people_id);
            }
            else if (mode == "visite")
            { // 巡诊模式
                sys_log("visite mode get!");
                mode_visite_server(people_id);
            }
        }
        else
        {
            err_log("request:%s dose not match.", req.id_mode.c_str());
        }

        ret.lang_end = true;

        return true;
    }

}

int main(int argc, char **argv)
{
    // 初始化 ros 节点
    ros::init(argc, argv, "llm_node");
    ros::NodeHandle nh;

    // ==================================================================================
    // 创建一个新的线程来处理"mode"节点的回调，因为这个回调会阻塞线程   begin >>>>>>>>>>>>>>>>>>>>
    // ==================================================================================
    // 创建自定义队列
    ros::CallbackQueue custom_queue;

    // 将 NodeHandle 绑定到这个队列
    ros::NodeHandle nh_private;
    nh_private.setCallbackQueue(&custom_queue);

    // 接受其他节点发来的消息 (对外提供的服务) -> 这个在整个过程中会阻塞
    ros::ServiceServer service = nh_private.advertiseService("people_id_lan", mmain::medicalNodeHandler);

    // 用独立线程处理这个队列
    std::thread queue_thread([&custom_queue]()
                             {
        while (ros::ok() && !hzc::exitFlag) {
            custom_queue.callAvailable(ros::WallDuration(0.1));  // 处理这个队列的回调
        }
        sys_log("exit 'custom queue thread'(people_id_lan service)."); });
    queue_thread.detach(); // 分离线程自己管自己的资源回收问题
    // ==================================================================================
    // 创建一个新的线程来处理"mode"节点的回调，因为这个回调会阻塞线程   end <<<<<<<<<<<<<<<<<<<<<<
    // ==================================================================================

    // 与对话节点服务通信
    mmain::dialog_client = nh.serviceClient<std_srvs::Empty>("/llm_dialog_srv");

    // 和 tts 通信
    mmain::tts_client = nh.serviceClient<llm_node::llm_tts>("/llm_tts_srv");

    // 和 asr 通信
    mmain::asr_client = nh.serviceClient<llm_node::llm_asr>("/llm_asr_srv");

    // 和 kws 通信
    mmain::kws_client = nh.serviceClient<std_srvs::Empty>("/llm_kws_srv");

    // 和 willing 通信
    mmain::willing_client = nh.serviceClient<llm_node::llm_goBack>("/llm_willing_srv");

    // 接收大语言模型的输出信息
    ros::Subscriber sub = nh.subscribe<std_msgs::String>(
        "llm_res_topic", 10, mmain::llmRecStreamData);

    // 向大语言模型发送问题
    // mmain::req_pub = nh.advertise<std_msgs::String>("huzhou_llm_req", 10);
    mmain::llm_client = nh.serviceClient<llm_node::llm_srv>("llm_server_srv");

    // 初始化 patient manager
    // 读取病人信息 到 内存中
    mmain::patient_manager = std::make_unique<PatientManager>(
        "/opt/patient/"
    );

    // 将地区映射到录音文件名称
    mmain::region_remap = std::make_unique<RegionRemap>(
        say_hello_path + std::string("remap.json"));

    sys_log("llm node init done.");

    ros::spin();

    hzc::signalHandler(0);
}
